﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Configuration.Provider;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;
using System.Web.Security;

namespace BlogEngine.SQLServer
{
    public class LinqMembershipProvider : MembershipProvider
    {
        private string connString;
        private string tablePrefix;
        private string schema;
        private string applicationName;
        private bool isMultiBlog = false;
        private MembershipPasswordFormat passwordFormat;

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (String.IsNullOrEmpty(name))
            {
                name = "LinqMembershipProvider";
            }

            if (Type.GetType("Mono.Runtime") != null)
            {
                // Mono dies with a "Unrecognized attribute: description" if a description is part of the config.
                if (!string.IsNullOrEmpty(config["description"]))
                {
                    config.Remove("description");
                }
            }
            else
            {
                if (string.IsNullOrEmpty(config["description"]))
                {
                    config.Remove("description");
                    config.Add("description", "SQL Server Membership Provider");
                }
            }

            base.Initialize(name, config);

            // Connection String
            if (config["connectionStringName"] == null)
            {
                config["connectionStringName"] = "BlogEngine";
            }
            connString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]].ConnectionString;
            config.Remove("connectionStringName");

            // Table Prefix
            if (config["tablePrefix"] == null)
            {
                config["tablePrefix"] = "be_";
            }
            tablePrefix = config["tablePrefix"];
            config.Remove("tablePrefix");

            if (config["multiblog"] != null)
            {
                isMultiBlog = config["multiblog"].ToLower() == "true";
                config.Remove("multiblog");
            }

            if (config["schema"] == null)
            {
                config["schema"] = "dbo";
            }
            schema = config["schema"].Replace(".", "");
            config.Remove("schema");

            // Application Name
            if (config["applicationName"] == null)
            {
                config["applicationName"] = "BlogEngine";
            }
            applicationName = config["applicationName"];
            config.Remove("applicationName");

            //Password Format
            if (config["passwordFormat"] == null)
            {
                config["passwordFormat"] = "Hashed";
                passwordFormat = MembershipPasswordFormat.Hashed;
            }
            else if (String.Compare(config["passwordFormat"], "clear", true) == 0)
            {
                passwordFormat = MembershipPasswordFormat.Clear;
            }
            else
            {
                passwordFormat = MembershipPasswordFormat.Hashed;
            }
            config.Remove("passwordFormat");

            // Throw an exception if unrecognized attributes remain
            if (config.Count > 0)
            {
                string attr = config.GetKey(0);
                if (!String.IsNullOrEmpty(attr))
                    throw new ProviderException("Unrecognized attribute: " + attr);
            } 
        }
        
        public override string ApplicationName
        {
            get
            {
                return applicationName;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            if (isCorrectPassword(username, oldPassword))
            {
                using (BlogData.BlogDataContext context = getNewContext())
                {
                    var users = from user in context.Users
                                where user.UserName == username
                                select user;
                    if (isMultiBlog)
                    {
                        users = from user in users
                                join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                                where blogUser.BlogId == Utils.GetBlogId()
                                select user;
                    }
                    BlogData.User userData = users.FirstOrDefault();
                    if (userData != null)
                    {
                        userData.Password = passwordFormat == MembershipPasswordFormat.Hashed ? Core.Utils.HashPassword(newPassword) : newPassword;
                        context.SubmitChanges();
                        return true;
                    }
                }
            }
            return false;
        }

        private bool isCorrectPassword(string username, string password)
        {
            string currentPassword;
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var users = from user in context.Users
                            where user.UserName == username
                            select user;
                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;
                }
                currentPassword = users.Select(u => u.Password).FirstOrDefault();
            }

            if (currentPassword == null)
                return false;

            if (currentPassword == string.Empty && password.ToLower() == "admin")
            {
                // special case used for password resetting
                return true;
            }

            return passwordFormat == MembershipPasswordFormat.Hashed 
                ? currentPassword == Core.Utils.HashPassword(password) 
                : currentPassword == password;
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser CreateUser(string username, string password, string email, 
                                                  string passwordQuestion, string passwordAnswer, 
                                                  bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            BlogData.User userData = new BlogData.User()
            {
                UserName = username,
                EmailAddress = email,
                Password = passwordFormat == MembershipPasswordFormat.Hashed ? Core.Utils.HashPassword(password) : password,
                LastLoginTime = DateTime.Now
            };
            using (BlogData.BlogDataContext context = getNewContext())
            {
                context.Users.InsertOnSubmit(userData);
                context.SubmitChanges();
                if (isMultiBlog)
                {
                    context.BlogUsers.InsertOnSubmit(new BlogData.BlogUser() { UserId = userData.UserID, BlogId = Utils.GetBlogId() });
                    context.SubmitChanges();
                }
                status = MembershipCreateStatus.Success;
                return getMembershipUser(username, email, DateTime.Now);
            }
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var userData = from user in context.Users
                               where user.UserName == username
                               select user;
                if (isMultiBlog)
                {
                    userData = from user in userData
                               join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                               where blogUser.BlogId == Utils.GetBlogId()
                               select user;
                }
                context.Users.DeleteAllOnSubmit(userData);
                context.SubmitChanges();
                return true;
            }
        }

        public override bool EnablePasswordReset
        {
            get { return false; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return false; }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            MembershipUserCollection users = new MembershipUserCollection();
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var usersData = from user in context.Users
                                select user;
                if (isMultiBlog)
                {
                    usersData = from user in usersData
                                join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                                where blogUser.BlogId == Utils.GetBlogId()
                                select user;
                }
                usersData.Skip(pageIndex * pageSize);
                usersData.Take(pageSize).ToList().ForEach(u =>
                    users.Add(getMembershipUser(u.UserName, u.EmailAddress, u.LastLoginTime ?? DateTime.MinValue)));
                
                totalRecords = users.Count;
                return users;
            }
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var users = from user in context.Users
                            where user.UserName == username
                            select user;
                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;
                }
                BlogData.User userData = users.FirstOrDefault();
                if (userData != null)
                {
                    return getMembershipUser(userData.UserName, userData.EmailAddress, userData.LastLoginTime ?? DateTime.MinValue);
                }
            }
            return null;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            return GetUser(providerUserKey.ToString(), userIsOnline);
        }

        public override string GetUserNameByEmail(string email)
        {
            if (string.IsNullOrEmpty(email))
                throw new ArgumentNullException("email");

            using (BlogData.BlogDataContext context = getNewContext())
            {
                var users = from user in context.Users
                            where user.EmailAddress == email
                            select user;
                if (isMultiBlog)
                {
                    users = from user in users
                            join blogUser in context.BlogUsers on user.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select user;
                }
                return users.Select(u => u.UserName).FirstOrDefault();
            }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return 5; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return 0; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return 4; }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return false; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return false; }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            using (BlogData.BlogDataContext context = getNewContext())
            {
                var users = from u in context.Users
                            where u.UserName == user.UserName
                            select u;
                if (isMultiBlog)
                {
                    users = from u in users
                            join blogUser in context.BlogUsers on u.UserID equals blogUser.UserId
                            where blogUser.BlogId == Utils.GetBlogId()
                            select u;
                }
                BlogData.User userData = users.FirstOrDefault();
                if (userData != null)
                {
                    userData.UserName = user.UserName;
                    userData.EmailAddress = user.Email;
                    context.SubmitChanges();
                }
            }
        }

        public override bool ValidateUser(string username, string password)
        {
            return isCorrectPassword(username, password);
        }

        private BlogData.BlogDataContext getNewContext()
        {
            return Utils.getNewMembershipContext(connString, tablePrefix, schema);
        }
        private MembershipUser getMembershipUser(string userName, string email, DateTime lastLogin)
        {
            MembershipUser user = new MembershipUser(
                                    Name,                       // Provider name
                                    userName,                   // Username
                                    userName,                   // providerUserKey
                                    email,                      // Email
                                    String.Empty,               // passwordQuestion
                                    String.Empty,               // Comment
                                    true,                       // isApproved
                                    false,                      // isLockedOut
                                    DateTime.Now,               // creationDate
                                    lastLogin,                  // lastLoginDate
                                    DateTime.Now,               // lastActivityDate
                                    DateTime.Now,               // lastPasswordChangedDate
                                    new DateTime(1980, 1, 1)    // lastLockoutDate
                                );
            return user;
        }
    }
}
